// tge.rs
// copyright zipxing@hotmail.com 2022~2023

//! Sprite对Buffer进一步封装
//! 是tge.rs游戏渲染最常用的基本组件，
//! 提供了set_border，draw_line, draw_circle等绘制函数
//! 画线的详细实现请参考util/shape.rs
//!
//! 也实现了加载和保存.sdl .esc文件的方法
//! 详细实现请参考util/image.rs
//!
//! esc文件存储终端模式下的字符图，存储esc终端控制序列
//! 和utf8文本，可以在终端下直接用cat查看效果:
//! $ cat assets/tetris/back.txt
//!
//! sdl文件存储SDL模式下的字符图，按行存储cell序列，
//! cell: 字符sym索引，前景色，背景色（SDL模式下背景色标识纹理）
//! $ cat assets/snake/back.sdl
//!

use crate::{
    render::buffer::Buffer,
    render::adapter::sdl::sdlsym,
    render::style::{Color, Style},
    render::image::*,
    util::shape::{circle, line, prepare_line},
    util::{FPoint, Point, Rect},
};
use bitflags::bitflags;
use log::info;
use std::{f32, io::Error};
mod sprites;
pub use sprites::Sprites;
mod sequence_frames;
pub use sequence_frames::SequenceFrames;

/// 定义一些常用的制表符号(文本模式下)
pub const SYMBOL_LINE: [&str; 37] = [
    "│", "║", "┃", "─", "═", "━", "┐", "╮", "╗", "┓", "┌", "╭", "╔", "┏", "┘", "╯", "╝", "┛", "└",
    "╰", "╚", "┗", "┤", "╣", "┫", "├", "╠", "┣", "┬", "╦", "┳", "┴", "╩", "┻", "┼", "╬", "╋",
];

// 边框的bigflags
bitflags! {
    pub struct Borders: u32 {
        const NONE   = 0b0000_0001;
        const TOP    = 0b0000_0010;
        const RIGHT  = 0b0000_0100;
        const BOTTOM = 0b0000_1000;
        const LEFT   = 0b0001_0000;
        const ALL    = Self::TOP.bits | Self::RIGHT.bits | Self::BOTTOM.bits | Self::LEFT.bits;
    }
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BorderType {
    Plain,
    Rounded,
    Double,
    Thick,
}

#[derive(Debug)]
pub enum DatFileError {
    Io(Error),
}

impl From<Error> for DatFileError {
    fn from(err: Error) -> DatFileError {
        DatFileError::Io(err)
    }
}

pub trait Widget {
    fn render(&self, buf: &mut Buffer);
}

#[derive(Clone)]
pub struct Sprite {
    pub content: Buffer,
    pub angle: f64,
    render_weight: i32,
}

impl Widget for Sprite {
    fn render(&self, buf: &mut Buffer) {
        if !self.is_hidden() {
            buf.merge(&self.content, true);
        }
    }
}

impl Sprite {
    pub fn new(x: u16, y: u16, width: u16, height: u16) -> Self {
        let area = Rect::new(x, y, width, height);
        let buffer = Buffer::empty(area);
        Self {
            content: buffer,
            angle: 0.0,
            render_weight: 1,
        }
    }

    pub fn set_sdl_content(&mut self, x: u16, y: u16, sym: u8, fg: u8, bg: u8) {
        self.content.set_str(
            x,
            y,
            sdlsym(sym),
            Style::default()
                .fg(Color::Indexed(fg))
                .bg(Color::Indexed(bg)),
        );
    }

    pub fn set_angle(&mut self, a: f64) {
        self.angle = a;
    }

    pub fn get_center_point(&self) -> FPoint {
        FPoint {
            x: self.content.area.x as f32 + self.content.area.width as f32 / 2.0,
            y: self.content.area.y as f32 + self.content.area.height as f32 / 2.0,
        }
    }

    pub fn set_hidden(&mut self, flag: bool) {
        if flag {
            self.render_weight = -1 * self.render_weight.abs();
        } else {
            self.render_weight = self.render_weight.abs();
        }
    }

    pub fn is_hidden(&self) -> bool {
        self.render_weight < 0
    }

    pub fn set_border(&mut self, borders: Borders, border_type: BorderType, style: Style) {
        // vertical horizontal
        // top_right top_left bottom_right bottom_left
        // vertical_left vertical_right horizontal_down horizontal_up
        // cross
        let lineidx: [usize; 11] = match border_type {
            BorderType::Plain => [0, 3, 6, 10, 14, 18, 22, 25, 28, 31, 34],
            BorderType::Rounded => [0, 3, 7, 11, 15, 19, 22, 25, 28, 31, 34],
            BorderType::Double => [1, 4, 8, 12, 16, 20, 23, 26, 29, 33, 35],
            BorderType::Thick => [2, 5, 9, 13, 17, 21, 24, 27, 30, 34, 36],
        };
        if borders.intersects(Borders::LEFT) {
            for y in 0..self.content.area.height {
                self.content.set_str(0, y, SYMBOL_LINE[lineidx[0]], style);
            }
        }
        if borders.intersects(Borders::TOP) {
            for x in 0..self.content.area.width {
                self.content.set_str(x, 0, SYMBOL_LINE[lineidx[1]], style);
            }
        }
        if borders.intersects(Borders::RIGHT) {
            let x = self.content.area.width - 1;
            for y in 0..self.content.area.height {
                self.content.set_str(x, y, SYMBOL_LINE[lineidx[0]], style);
            }
        }
        if borders.intersects(Borders::BOTTOM) {
            let y = self.content.area.height - 1;
            for x in 0..self.content.area.width {
                self.content.set_str(x, y, SYMBOL_LINE[lineidx[1]], style);
            }
        }
        if borders.contains(Borders::RIGHT | Borders::BOTTOM) {
            self.content.set_str(
                self.content.area.width - 1,
                self.content.area.height - 1,
                SYMBOL_LINE[lineidx[4]],
                style,
            );
        }
        if borders.contains(Borders::RIGHT | Borders::TOP) {
            self.content.set_str(
                self.content.area.width - 1,
                0,
                SYMBOL_LINE[lineidx[2]],
                style,
            );
        }
        if borders.contains(Borders::LEFT | Borders::BOTTOM) {
            self.content.set_str(
                0,
                self.content.area.height - 1,
                SYMBOL_LINE[lineidx[5]],
                style,
            );
        }
        if borders.contains(Borders::LEFT | Borders::TOP) {
            self.content.set_str(0, 0, SYMBOL_LINE[lineidx[3]], style);
        }
    }

    pub fn copy_content(&mut self, sp: &Sprite) {
        let backup_area = self.content.area;
        //先把坐标设置为(0, 0), 以便跟boxes进行merge
        self.content.area = Rect::new(0, 0, backup_area.width, backup_area.height);
        self.content.reset();
        self.content.merge(&sp.content, false);

        //merge完之后，设置正确的位置
        self.content.area = backup_area;
    }

    pub fn set_pos(&mut self, x: u16, y: u16) {
        self.content.area = Rect::new(x, y, self.content.area.width, self.content.area.height);
    }

    pub fn draw_circle(
        &mut self,
        x0: u16,
        y0: u16,
        radius: u16,
        sym: &str,
        fg_color: u8,
        bg_color: u8,
    ) {
        for p in circle(x0, y0, radius) {
            if (p.0 as u16) < self.content.area.width && (p.1 as u16) < self.content.area.height {
                self.content.set_str(
                    p.0 as u16,
                    p.1 as u16,
                    sym,
                    Style::default()
                        .fg(Color::Indexed(fg_color))
                        .bg(Color::Indexed(bg_color)),
                );
            }
        }
    }

    pub fn draw_line(
        &mut self,
        px0: u16,
        py0: u16,
        px1: u16,
        py1: u16,
        sym: Option<Vec<Option<u8>>>,
        fg_color: u8,
        bg_color: u8,
    ) {
        let (x0, y0, x1, y1) = prepare_line(px0, py0, px1, py1);
        // start, end, v, h, s, bs...
        let mut syms: Vec<Option<u8>> = vec![None, None, Some(119), Some(116), Some(77), Some(78)];
        if let Some(s) = sym {
            syms = s;
        }
        for p in line(x0, y0, x1, y1) {
            let x = p.0 as u16;
            let y = p.1 as u16;
            let sym = syms[p.2 as usize];
            if let Some(s) = sym {
                if x < self.content.area.width && y < self.content.area.height {
                    self.content.set_str(
                        x,
                        y,
                        sdlsym(s),
                        Style::default()
                            .fg(Color::Indexed(fg_color))
                            .bg(Color::Indexed(bg_color)),
                    );
                }
            }
        }
    }

    #[cfg(feature = "sdl")]
    pub fn save_sdl(&self, fpath: &str) {
        save_sdlfile(&self.content, fpath).unwrap_or_else(|e| {
            info!("save esc file{} error{:?}!", fpath, e);
        });
    }

    #[cfg(feature = "sdl")]
    pub fn load_sdl(&mut self, fpath: &str, off_x: u16, off_y: u16) -> Point {
        let mut pr = Point { x: 0, y: 0 };
        match load_sdlfile(&mut self.content, fpath, off_x, off_y) {
            Ok(p) => {
                pr.x = p.x;
                pr.y = p.y;
            }
            Err(e) => {
                info!("load sdlfile({}) error{:?}!", fpath, e);
            }
        }
        pr
    }

    pub fn save_esc(&self, fpath: &str) {
        save_escfile(&self.content, fpath).unwrap_or_else(|e| {
            info!("save esc file{} error{:?}!", fpath, e);
        });
    }

    pub fn load_esc(&mut self, fpath: &str, off_x: u16, off_y: u16) -> Point {
        let mut pr = Point { x: 0, y: 0 };
        match load_escfile(&mut self.content, fpath, off_x, off_y) {
            Ok(p) => {
                pr.x = p.x;
                pr.y = p.y;
            }
            Err(e) => {
                info!("load escfile({}) error{:?}!", fpath, e);
            }
        }
        pr
    }
}
